<?
/**
 * Copyright 2007 Melange.
 *
 * This file is part of PHP-MVC.
 *
 * PHP-MVC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * PHP-MVC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PHP-MVC; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @category    Melange
 * @package     php-mvc
 * @copyright   Copyright (c) 2007 Jeroen Simons. All rights reserved
 * @author      Jeroen Simons <jeroen@melange.nl>
 * @link        http://www.melange.nl/
 *
 */

require_once("mvc/Config.php");

/**
 * MVC
 *
 * @category   Melange
 * @package    php-mvc
 * @copyright  Copyright (c) 2007 Jeroen Simons. All rights reserved
 * @license    http://www.gnu.org/licenses/licenses.html#GPL    The GNU General Public License
 */
class MVC {


    /**
     * Logger
     */
    private $log;


    // -------------------------------------------------------------- Constants


    /**
     * The request attribute under which we forward a Java exception
     * (as an object of type Throwable) to an error page.
     */
    const EXCEPTION_ATTR =
            "javax.servlet.error.exception";


    /**
     * The request attributes key under which Struts custom tags might store a
     * <code>Throwable</code> that caused them to report a JspException at
     * runtime.  This value can be used on an error page to provide more
     * detailed information about what really went wrong.
     */
    const EXCEPTION_KEY =
        "nl.melange.phpmvc.action.EXCEPTION";


    const CANCEL_KEY = "nl_melange_phpmvc_action_CANCEL_KEY";


    /**
     * The context attributes key under which our <code>ActionServlet</code>
     * instance will be stored.
     */
    const MVC_KEY = "nl.melange.phpmvc.action.MVC";

    /**
     * <p>The base of the context attributes key under which our
     * <code>ModuleConfig</code> data structure will be stored.  This will be
     * suffixed with the actual module prefix (including the leading "/"
     * character) to form the actual attributes key.</p>
     *
     * <p>For each request processed by the controller servlet, the
     * <code>ModuleConfig</code> object for the module selected by the request
     * URI currently being processed will also be exposed under this key as a
     * request attribute.</p>
     *
     * @since Struts 1.1
     */
    const MODULE_KEY = "nl.melange.phpmvc.action.MODULE";

    /**
     * The ServletContext attribute under which we store the module prefixes
     * String[].
     *
     */
    const MODULE_PREFIXES_KEY = "nl.melange.phpmvc.globals.MODULE_PREFIXES";

    /**
     * The request attributes key under which your action should store an
     * <code>org.apache.struts.action.ActionMessages</code> object, if you are
     * using the corresponding custom tag library elements.
     *
     * @since Struts 1.1
     */
    const MESSAGE_KEY = "nl.melange.phpmvc.action.ACTION_MESSAGE";

    /**
     * The request attributes key under which your action should store an
     * <code>org.apache.struts.action.ActionErrors</code> object, if you are
     * using the corresponding custom tag library elements.
     */
    const ERROR_KEY = "nl.melange.phpmvc.action.ACTION_ERROR";

    /**
     * The request attributes key under which our <code>org.apache.struts.ActionMapping</code>
     * instance is passed.
     */
    const MAPPING_KEY = "nl.melange.phpmvc.action.mapping.instance";


    /**
     * The request attributes key under which our <code>org.apache.struts.ActionMapping</code>
     * instance is passed.
     */
    const FORM_KEY = "nl.melange.phpmvc.action.form.instance";

    /**
     * <p>The base of the context attributes key under which our module
     * <code>MessageResources</code> will be stored.  This will be suffixed
     * with the actual module prefix (including the leading "/" character) to
     * form the actual resources key.</p>
     *
     * <p>For each request processed by the controller servlet, the
     * <code>MessageResources</code> object for the module selected by the
     * request URI currently being processed will also be exposed under this
     * key as a request attribute.</p>
     */
    const MESSAGES_KEY = "nl.melange.phpmvc.action.MESSAGE";

    /**
     * The session attributes key under which the user's selected
     * <code>java.util.Locale</code> is stored, if any.  If no such attribute
     * is found, the system default locale will be used when retrieving
     * internationalized messages.  If used, this attribute is typically set
     * during user login processing.
     */
    const LOCALE_KEY = "nl.melange.phpmvc.action.LOCALE";


    /**
     * The session attributes key under which our transaction token is stored,
     * if it is used.
     */
    const TRANSACTION_TOKEN_KEY = "nl.melange.phpmvc.action.TOKEN";


    /**
     * The property under which a transaction token is reported.
     */
    const TOKEN_KEY = "nl_melange_phpmvc_action_TOKEN";



    // ----------------------------------------------------- Instance Variables


    private $prefixes = array();
    private $paths    = array();
    private $views    = array();
    private $helpers  = array();
    private $applicationContext = null;


    // ----------------------------------------------------------- Constructors


    public function __construct(Context $application) {

        $this->log =& LoggerManager::getLogger("MVC");

        $this->applicationContext = $application;

        // Add the MVC (ActionServlet) to the ApplicationContext
        $this->applicationContext->setAttribute(MVC::MVC_KEY, $this);

        $this->init();
    }


    // --------------------------------------------------------- Public Methods


    public function getApplicationContext() {
        return $this->applicationContext;
    }


    /**
     * <p>Initialize this servlet.  Most of the processing has been factored
     * into support methods so that you can override particular functionality
     * at a fairly granular level.</p>
     *
     * @throws ServletException if we cannot configure ourselves correctly
     */
    public function init() {

        $configPrefix = "config/";
        $configPrefixLength = strlen($configPrefix);

        $this->addModuleConfig("", $this->applicationContext->getInitParameter(
            substr($configPrefix, 0, -1)));

        $moduleConfig = $this->getMultiModuleConfig($this->paths[""]);
        $moduleConfig->setPrefix("");

        $this->initModuleMessageResources($moduleConfig);

        foreach($this->applicationContext->getInitParameterNames() as $name) {
            if (strpos($name, $configPrefix) === 0) {
                $prefix = substr($name, $configPrefixLength);
                $this->addModuleConfig($prefix,
                    $this->applicationContext->getInitParameter($name));
            }
        }

    }


    public function addModuleConfig($prefix, $path) {
        $this->initModulePrefixes($prefix);
        $this->paths[$prefix] = $path;
    }


    protected function initModulePrefixes($prefix) {
        if(strlen($prefix) > 0)
            $this->prefixes[] = $prefix;
    }


    /*
     * Set the View Path (Location of the Zend View Templates)
     * Todo: Move to the generator ?
     */
    public function setViewPath($path) {
        $this->views[] = $path;
    }


    public function addViewPath($path) {
        $this->views[] = $path;
    }


    /*
     * Add a Helper Path (Location of the Zend View Helpers)
     * Todo: Move to the generator ?
     */
    public function addHelperPath($path, $classPrefix) {
        $this->helpers[] = array("path"=>$path, "classPrefix"=>$classPrefix);
    }


    public function process($request, $response) {

        $moduleConfig = $this->selectModule($request);

        $this->initModuleMessageResources($moduleConfig);

        require_once("mvc/view/View.php");
        $moduleConfig->setView(new Zend_View());
        
//        $moduleConfig->getView()->setResources(
//            $this->applicationContext->getInitParameter(Context::RESOURCES)
//        );

        $this->processView($moduleConfig);

        $this->processHelpers($moduleConfig);

        require_once("mvc/action/RequestProcessor.php");
        $processor = new RequestProcessor($moduleConfig);
        $processor->process($request, $response);

    }


    // --------------------------------------------------------- Protected Methods


    protected function processView(&$moduleConfig) {

        $this->views = array_merge($this->views, $moduleConfig->getViews());

        foreach($this->views as $view)
            $moduleConfig->getView()->addScriptPath($view);

        $moduleConfig->getView()->assign("applicationContext", $this->applicationContext);
        $moduleConfig->getView()->assign("moduleConfig", $moduleConfig);
    }


    protected function processHelpers(&$moduleConfig) {

        $this->helpers = array_merge($moduleConfig->getHelpers(), $this->helpers);

        foreach($this->helpers as $helper) {
            $moduleConfig->getView()->addHelperPath($helper["path"], $helper["classPrefix"]);
        }
    }


    protected function selectModule($request) {

        // Compute module name
        $prefix = $this->getModuleName(
            substr($request->getPathInfo(), 1)
        );

        $moduleConfig = $this->getMultiModuleConfig($this->paths[$prefix]);

        if($moduleConfig === null)
            throw new Exception("Module config file not found in " . $this->paths[$prefix]);

        $moduleConfig->setPrefix($prefix);

        return $moduleConfig;

    }

    protected function getMultiModuleConfig($path) {

        $paths = explode(",", $path);
        $moduleConfig = null;

        foreach($paths as $path) {

            $path = trim($path);
            if(strpos($path, "/") === 0) {

                // Absolute config path
                $config = include($path);

            } else {

                // Resources paths, comma seperated.
//                $resources = explode(",", $this->applicationContext->getInitParameter(
//                    Context::RESOURCES));

//                foreach($resources as $resource) {
//                    $resource = trim($resource);


                    if(Context::isReadable($path)) {
                        $config = include(Context::getFilePath($path));
                    }

//                    if(file_exists($resource."/".$path)) {
//                        $config = include($resource."/".$path);
//                        break;
//                    }
//                }
            }

            if($moduleConfig === null) {
                $moduleConfig = $config;
            } else {


                $this->log->debug("Merging!");

                // Merge the configs
                $moduleConfig->merge($config);

            }

        }

        return $moduleConfig;

    }


    protected function getModuleName($matchPath) {

        if ($this->log->isDebugEnabled()) {
            $this->log->debug("Get module name for path " . $matchPath);
        }

        $prefix = ""; // Initialize prefix before we try lookup
        $prefixes = $this->prefixes;

        // Get all other possible prefixes
        $lastSlash = 0; // Initialize before loop

        while ($prefix == "" && (($lastSlash = strrpos($matchPath, "/")) > 0)) {

            // We may be in a non-default module.  Try to get it's prefix.
            $matchPath = substr($matchPath, 0, $lastSlash);

            if ($this->log->isDebugEnabled()) {
                $this->log->debug("Get module name for path " . $matchPath);
            }

            // Match against the list of module prefixes
            for ($i = 0; $i < count($this->prefixes); $i++) {
                if ($matchPath == $this->prefixes[$i]) {
                    $prefix = $this->prefixes[$i];
                    break;
                }
            }
        }

        if ($this->log->isDebugEnabled()) {
            $this->log->debug("Module name found: " . ($prefix == "" ? "default" : $prefix));
        }

        return $prefix;

    }


    /**
     * <p>Initialize the application <code>MessageResources</code> for the
     * specified module.</p>
     *
     * @param config ModuleConfig information for this module
     * @throws ServletException if initialization cannot be performed
     * @since Struts 1.1
     */
    protected function initModuleMessageResources(ModuleConfig $config) {

        $mrcs = $config->findMessageResourcesConfig();

        while(list($key, $mrc) = each($mrcs)) {

            if ($this->log->isDebugEnabled()) {
                $this->log->debug("Initializing module path '" . $config->getPrefix()
                    . "' message resources from '" . $mrc->getParameter()
                    . "'");
                $this->log->debug("Setting application attr '" . $mrc->getKey() . $config->getPrefix() . "'");
            }

            require_once("mvc/util/MessageResources.php");

            $messageResources = new MessageResources($mrc->getParameter(), $mrc->getNull());

            $this->applicationContext->setAttribute(
                $mrc->getKey() . $config->getPrefix(),
                $messageResources
            );
        }
    }

}
?>